/******************************************************************************
 Copyright (c) 2008-2022 Qualcomm Technologies International, Ltd.
 All Rights Reserved.
 Qualcomm Technologies International, Ltd. Confidential and Proprietary.

 REVISION:      $Revision: #9 $
******************************************************************************/

#include "csr_synergy.h"

#ifndef EXCLUDE_CSR_BT_L2CA_MODULE

#include "csr_bt_cm_lib.h"
#include "csr_bt_cm_private_lib.h"
#include "csr_bt_cm_l2cap.h"
#include "csr_bt_cm_dm.h"
#include "csr_bt_cm_util.h"
#ifndef EXCLUDE_CSR_BT_SC_MODULE
#include "csr_bt_sc_private_lib.h"
#endif

static void cmPendingL2caMsgFree(void *msg)
{
     L2CA_FreePrimitive(msg);
}

static CsrBool cmRejectPendingL2caAutoConnectInd(cmInstanceData_t *cmData, void *msg, void* pContext)
{
    CsrBool processed = FALSE;

    if (pContext)
    {
        CsrBtConnId btConnId;
        cmL2caConnElement* theElement;
        L2CA_AUTO_CONNECT_IND_T* prim;
        l2ca_cid_t cid = 0; /* cid is passed in context */

        cid        = *((l2ca_cid_t*)pContext);
        prim       = (L2CA_AUTO_CONNECT_IND_T*) msg;
        btConnId   = CM_CREATE_L2CA_CONN_ID(cid); 
        theElement = CM_FIND_L2CA_ELEMENT(CsrBtCmL2caFindL2caConnElementFromBtConnId, &(btConnId)); 

        if (CsrBtBdAddrEq(&prim->bd_addr, &theElement->cmL2caConnInst->deviceAddr) && 
            (prim->psm_local == theElement->cmL2caConnInst->psm))
        {
            L2CA_AutoConnectRsp(prim->identifier,
                                prim->cid,
                                L2CA_CONNECT_REJ_RESOURCES,
                                CM_L2CA_INCOMING_CONNECT_REJECTED_CTX,
                                0, /*conftab_length*/
                                NULL); /*conftab*/
            processed = TRUE;
        }
    }
    return(processed);
}

static void csrBtCmL2caDisconnectHandler(cmInstanceData_t   *cmData,
                                         CsrBool            localTerminated,
                                         l2ca_cid_t         cid,
                                         l2ca_identifier_t  signalId,
                                         CsrBtReasonCode    reason)
{
    CsrBtConnId btConnId            = CM_CREATE_L2CA_CONN_ID(cid);
    cmL2caConnElement * theElement  = CM_FIND_L2CA_ELEMENT(CsrBtCmL2caFindL2caConnElementFromBtConnId, &(btConnId));
    CsrBool restoreSmQueue = FALSE;

    if (theElement)
    {
        cmL2caConnInstType *l2CaConnection = theElement->cmL2caConnInst;
        CsrBtDeviceAddr    remoteAddr      = l2CaConnection->deviceAddr;

        switch(l2CaConnection->state)
        {
            case CSR_BT_CM_L2CAP_STATE_CONNECTED :
            {
                /* The peer device has requested that the L2CAP is release or the CM
                 * has release it direct. Accept this request, clear the l2cap 
                 * connection table and notify the application, Note always 
                 * consider as a remote disconnect */
                CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                                       CSR_BT_CM_L2CAP_STATE_IDLE);
                CsrBtCmModeChangeReqMsgSend(cmData, l2CaConnection->deviceAddr);
                /* If a remote device is disconnecting A2DP L2CAP signaling connection,
                 * reject any pending A2DP media L2CAP connection from the same device */
                if (!localTerminated && (l2CaConnection->psm == CSR_BT_AVDTP_PSM))
                {
                    CmHandlePendingPrim(cmData,
                                        L2CAP_PRIM,
                                        L2CA_AUTO_CONNECT_IND,
                                        (void*)&cid, /* pContext */
                                        cmRejectPendingL2caAutoConnectInd,
                                        cmPendingL2caMsgFree);
                }
                CsrBtCmL2capDisconnectIndMsgSend(cmData,
                                                 theElement,
                                                 reason,
                                                 CSR_BT_SUPPLIER_L2CAP_DISCONNECT,
                                                 FALSE,
                                                 signalId);

                CsrBtCmWriteAutoFlushTimeout(cmData, &remoteAddr);
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_RELEASE:
            {
                CsrBtCmL2caFinalReleaseHandler(cmData, 
                                               theElement, 
                                               localTerminated,
                                               reason, 
                                               CSR_BT_SUPPLIER_L2CAP_DISCONNECT, 
                                               localTerminated,
                                               signalId);
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_CONNECT:
            { /* The  L2CAP is release while trying to establish it. Make sure that
                 the local device accept SNIFF before notifying the
                 application and cleaning up the l2cap connection table */

#ifdef CSR_BT_INSTALL_CM_INTERNAL_LP_CONTROL
                CsrUint8 numOfConnection = CsrBtCmReturnNumOfConnectionsToPeerDevice(cmData, 
                                                                                     l2CaConnection->deviceAddr);
#endif
                if (!cmData->l2caVar.cancelConnect)
                {
                    cmData->smVar.arg.result.code        = reason;
                    cmData->smVar.arg.result.supplier    = CSR_BT_SUPPLIER_L2CAP_DISCONNECT;
                }
                else
                {
                    cmData->smVar.arg.result.code        = CSR_BT_RESULT_CODE_CM_CANCELLED;
                    cmData->smVar.arg.result.supplier    = CSR_BT_SUPPLIER_CM;
                }

#ifdef CSR_BT_INSTALL_CM_INTERNAL_LP_CONTROL
                if (numOfConnection)
                { /* Currently there is at least one more connection attach to the
                     device address. Ensure that SNIFF is supported */

                    /* Since we need to update link policy settings, the lock needs be released by
                     * the respective complete handler for LP settings. */
                    if (l2CaConnection->transportType == BREDR_ACL)
                    {
                        CsrBtCmDmWriteLpSettingsReqMsgSend(l2CaConnection->deviceAddr,
                                                           L2CAP_PLAYER,
                                                           LINK_POLICY_MASK /* enable all settings */);
                    }
                    else
                    { /* For LE ACL, restore the local service manager queue*/
                        CsrBtCmServiceManagerLocalQueueHandler(cmData);
                    }
                }
                else
#endif /* CSR_BT_INSTALL_CM_INTERNAL_LP_CONTROL */
                { /* There is no other connection attach to the device address.
                     Inform the application, restore the local service manager
                     queue, and clear up the l2cap connection table */
                    CsrBtCmL2caConnectCfmMsgHandler(cmData, 
                                                    theElement, 
                                                    cmData->smVar.arg.result.code, 
                                                    cmData->smVar.arg.result.supplier);
                }
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_CONNECT_ACCEPT:
            { /* The L2CAP connection is release while accepting a connection. 
                 Keep the link discoverable and restore the local service manager 
                 and DM queue */
                CsrBtCmL2capAcceptFailClearing(cmData, l2CaConnection);
                if (localTerminated)
                {
                    CsrBtCmWriteAutoFlushTimeout(cmData, &remoteAddr);
                }
                restoreSmQueue = TRUE;
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_SAVE_DISCONNECT:
            { /* A disconnect request has been restored from the service manager
                 queue. Accept the release request clear the l2cap connection
                 table and notify the application. Note always consider as a 
                 remote disconnect */
                CsrBtCmL2capDisconnectIndMsgSend(cmData, theElement, 
                                                 reason, 
                                                 CSR_BT_SUPPLIER_L2CAP_DISCONNECT,
                                                 FALSE,
                                                 signalId);
                CsrBtCmWriteAutoFlushTimeout(cmData, &remoteAddr);
                restoreSmQueue = TRUE;
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_CONNECT_ACCEPT_FINAL:
            { /* The L2CAP connection is release while removing the COD or
                 making the device unconnectable, save this message 
                 until this process is finish. Note when this process is
                 finish and this message is restored, the state will always be
                 CSR_BT_CM_L2CAP_STATE_CONNECTED */
                CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                                       CSR_BT_CM_L2CAP_STATE_SAVE_DISCONNECT);
                CsrMessageQueuePush(&cmData->smVar.saveQueue, L2CAP_PRIM, cmData->recvMsgP);
                cmData->recvMsgP    = NULL;
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_RELEASE_INIT:
            { /* Crossover between local and remote disconnect.                     */
                cmData->smVar.arg.result.code        = reason;
                cmData->smVar.arg.result.supplier    = CSR_BT_SUPPLIER_L2CAP_DISCONNECT;
                CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                                       CSR_BT_CM_L2CAP_STATE_RELEASE_INIT_XOVER);
                break;
            }
            case CSR_BT_CM_L2CAP_STATE_RELEASE_FINAL:
            { /* Just Ignore it.CM has already receive a a L2CA_DISCONNECT_CFM/IND */
                break;
            }
            default:
            {
                break;
            }
        }
    }
    else
    { /* No owner just ignore the message and don't unlock SM queue. */
        ;
    }

    if (restoreSmQueue)
    {
        CsrBtCmServiceManagerLocalQueueHandler(cmData);
    }
}

void CsrBtCmL2caFinalReleaseHandler(cmInstanceData_t       *cmData,
                                    cmL2caConnElement      *theElement,
                                    CsrBool                 flush,
                                    CsrBtReasonCode         reasonCode,
                                    CsrBtSupplier           reasonSupplier,
                                    CsrBool                 localTerminated,
                                    l2ca_identifier_t       signalId)
{
    cmL2caConnInstType *l2CaConnection = theElement->cmL2caConnInst;

#ifdef CSR_BT_INSTALL_CM_INTERNAL_LP_CONTROL
    CsrUint8 numOfConnection = CsrBtCmReturnNumOfConnectionsToPeerDevice(cmData,
                                                                         l2CaConnection->deviceAddr);

    if (numOfConnection)
    { /* Currently there is at least one more connection attach
         to the given device address. Ensure that the SNIFF
         mode is supported */
        CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                               CSR_BT_CM_L2CAP_STATE_RELEASE_FINAL);
        cmData->smVar.arg.result.code        = reasonCode;
        cmData->smVar.arg.result.supplier    = reasonSupplier;
        if (l2CaConnection->transportType == BREDR_ACL)
        {
            CsrBtCmDmWriteLpSettingsReqMsgSend(l2CaConnection->deviceAddr,
                                               L2CAP_PLAYER,
                                               LINK_POLICY_MASK /* enable all settings */);
        }
        else
        {
            CsrBtCmL2capDisconnectIndMsgSend(cmData,
                                             theElement,
                                             reasonCode,
                                             reasonSupplier,
                                             localTerminated,
                                             signalId);
            /* For LE ACL, restore the local service manager queue */
            CsrBtCmServiceManagerLocalQueueHandler(cmData);
        }
    }
    else
#endif /* CSR_BT_INSTALL_CM_INTERNAL_LP_CONTROL */
    { /* Currently there is no other connections attach to the
         given Bluetooth address. Send disconnect ind to the
         application, clean up the l2cap connection table, and
         restore the local service manager queue */
        CsrBtDeviceAddr remoteAddr = l2CaConnection->deviceAddr;

        CsrBtCmL2capDisconnectIndMsgSend(cmData,
                                         theElement,
                                         reasonCode,
                                         reasonSupplier,
                                         localTerminated,
                                         signalId);

        if (flush)
        {
            CsrBtCmWriteAutoFlushTimeout(cmData, &remoteAddr);
        }
        CsrBtCmServiceManagerLocalQueueHandler(cmData);
    }
}

void CsrBtCmL2capDisconnectIndMsgSend(cmInstanceData_t     *cmData,
                                      cmL2caConnElement    *theElement,
                                      CsrBtReasonCode       reasonCode,
                                      CsrBtSupplier         reasonSupplier,
                                      CsrBool               localTerminated,
                                      l2ca_identifier_t     signalId)
{
    
    cmL2caConnInstType *l2CaConnection = theElement->cmL2caConnInst;
    CsrBtCmL2caDisconnectInd *prim  = (CsrBtCmL2caDisconnectInd *) CsrPmemAlloc(sizeof(CsrBtCmL2caDisconnectInd));

#ifdef CSR_BT_INSTALL_CM_DISABLE_AUTO_DISC_RESP
    /* No need for CM to respond, as application will take care of it. */
    cmData->sendDiscRsp = FALSE;
#endif

    prim->type              = CSR_BT_CM_L2CA_DISCONNECT_IND;
    prim->btConnId          = l2CaConnection->btConnId;
    prim->reasonCode        = reasonCode;
    prim->reasonSupplier    = reasonSupplier;
    prim->localTerminated   = localTerminated;
    prim->context           = l2CaConnection->context;
    prim->l2caSignalId      = signalId;
    CsrBtCmPutMessage(l2CaConnection->appHandle, prim);

    if (reasonCode == CSR_BT_RESULT_CODE_CM_UNKNOWN_CONNECTION_IDENTIFIER &&
        reasonSupplier == CSR_BT_SUPPLIER_CM)
    {
        ;
    }
    else
    {
        CsrUint8        numOfOutgoing;
        CsrUint8        numOfIncoming;
        CsrBtDeviceAddr deviceAddr;

        numberOfSecurityRegister(cmData, l2CaConnection->psm, l2CaConnection->deviceAddr, &numOfOutgoing, &numOfIncoming);

        if(l2CaConnection->remotePsm != NO_REMOTE_PSM)
        { /* The local device has initiate the connection, deregistered outgoing */
            if (numOfOutgoing == 1)
            { /* Unregister The Outgoing security setting */
                dm_sm_unregister_outgoing_req(CSR_BT_CM_IFACEQUEUE,
                                              0, /* context */
                                              &l2CaConnection->deviceAddr,
                                              (l2CaConnection->transportType == BREDR_ACL) ? SEC_PROTOCOL_L2CAP : SEC_PROTOCOL_LE_L2CAP,
                                              l2CaConnection->psm,
                                              NULL);
            }
            else
            { /* There is more that need this security setting */
                ;
            }
        }
        else
        { /* The remote device has initiate the connection, deregistered incoming */
            if (numOfIncoming == 1)
            { /* Unregister The Incoming security setting */
                CsrBtScDeregisterReqSend(
                    (l2CaConnection->transportType == BREDR_ACL) ? SEC_PROTOCOL_L2CAP : SEC_PROTOCOL_LE_L2CAP,
                    l2CaConnection->psm);
            }
            else
            { /* There is more that need this security setting */
                ;
            }
        }
        if (l2CaConnection->dataPriority != CSR_BT_CM_PRIORITY_NORMAL)
        {
            deviceAddr = l2CaConnection->deviceAddr;
        }
        else
        {
            CsrBtBdAddrZero(&deviceAddr);
        }
        CsrBtCmL2capClearL2capTableIndex(cmData, &(theElement->cmL2caConnInst));
    }
}

void CsrBtCmL2caDisconnectReqHandler(cmInstanceData_t *cmData)
{ /* Request to disconnect L2CAP channel */
    CsrBtCmL2caDisconnectReq    * cmPrim = (CsrBtCmL2caDisconnectReq *) cmData->recvMsgP;
    cmL2caConnElement * theElement = CM_FIND_L2CA_ELEMENT(CsrBtCmL2caFindL2caConnElementFromBtConnId, &(cmPrim->btConnId));

    if (theElement)
    {
        cmL2caConnInstType *l2CaConnection = theElement->cmL2caConnInst;
        cmData->l2caVar.activeElemId       = theElement->elementId;

        if (l2CaConnection->state == CSR_BT_CM_L2CAP_STATE_CONNECTED)
        {
#ifdef CSR_BT_INSTALL_LP_POWER_TABLE
            CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                                  CSR_BT_CM_L2CAP_STATE_RELEASE);
            /* Release the l2cap connection on restoring the local DM queue */
            L2CA_DisconnectReq(CM_GET_UINT16ID_FROM_BTCONN_ID(l2CaConnection->btConnId));
#else /* CSR_BT_INSTALL_LP_POWER_TABLE */
            /* The profile has a know l2cap connection. Before the CM can release
            the l2cap connection, it must insure that the host don't change
            the link policy. */
            CSR_BT_CM_STATE_CHANGE(l2CaConnection->state,
                                  CSR_BT_CM_L2CAP_STATE_RELEASE_INIT);
            CmDmSwitchToActiveLinkPolicy(cmData,
                                         &l2CaConnection->deviceAddr,
                                         L2CAP_PLAYER,
                                         CM_PT_STATE_L2CAP_CONNECT_OR_DISCONNECT,
                                         &l2CaConnection->requestedMode);
#endif /* !CSR_BT_INSTALL_LP_POWER_TABLE */
        }
        else
        { /* The profile try to disconnect a connection that is not up running.
             Build and send CSR_BT_CM_L2CA_DISCONNECT_IND with ERROR, and restore the
             local service manager queue */
            theElement->cmL2caConnInst->context = cmPrim->context;
            CsrBtCmL2capDisconnectIndMsgSend(cmData,
                                             theElement,
                                             (CsrBtReasonCode) CSR_BT_RESULT_CODE_CM_UNKNOWN_CONNECTION_IDENTIFIER,
                                             CSR_BT_SUPPLIER_CM,
                                             TRUE,
                                             0); /* Signal ID is only used for sending response */

            CsrBtCmServiceManagerLocalQueueHandler(cmData);
        }
    }
    else
    { /* Just resolve the local service manager queue */
        CsrBtCmServiceManagerLocalQueueHandler(cmData);
    }
}

#ifdef CSR_BT_INSTALL_CM_DISABLE_AUTO_DISC_RESP
void CsrBtCmL2caDisconnectRspHandler(cmInstanceData_t *cmData)
{
    CsrBtCmL2caDisconnectRsp *rsp = (CsrBtCmL2caDisconnectRsp*) cmData->recvMsgP;

#ifdef CSR_STREAMS_ENABLE
    /* A valid sink indicates that the confirmation is required
     * in order to delete the related streams. */
    if (SinkIsValid(StreamL2capSink(CM_GET_UINT16ID_FROM_BTCONN_ID(rsp->btConnId))))
#endif /* CSR_STREAMS_ENABLE */
    {
        L2CA_DisconnectRsp(rsp->identifier, CM_GET_UINT16ID_FROM_BTCONN_ID(rsp->btConnId));
    }
}
#endif

void CsrBtCmL2caDisconnectCfmHandler(cmInstanceData_t *cmData)
{ /* Confirmation of request to disconnect L2CAP channel, and restore the local
     L2CAP queue */
    L2CA_DISCONNECT_CFM_T   * prim = (L2CA_DISCONNECT_CFM_T *) cmData->recvMsgP;
    csrBtCmL2caDisconnectHandler(cmData,
                                 TRUE,
                                 prim->cid,
                                 0,
                                 (CsrBtReasonCode) prim->result);

#ifdef CSR_TARGET_PRODUCT_VM
    if (prim->result == L2CA_DISCONNECT_LINK_TRANSFERRED && cmData->recvMsgP)
    {
        /* ULCONV_TODO: This is a dummy indication formulated by CM while handling DM_BAD_MESSAGE_IND.
         * This must be released as normal Synergy primitive rather than as Bluestack primitive. */
        SynergyMessageFree(L2CAP_PRIM, cmData->recvMsgP);
        cmData->recvMsgP = NULL;
    }
#endif
}

void CsrBtCmL2caDisconnectIndHandler(cmInstanceData_t *cmData)
{ /* Indication of request to disconnect L2CAP channel. Send a Respond back
     to accept this demand, and inform the application */
    L2CA_DISCONNECT_IND_T * prim = (L2CA_DISCONNECT_IND_T *) cmData->recvMsgP;

#ifdef CSR_BT_INSTALL_CM_DISABLE_AUTO_DISC_RESP
    /* Default behavior is for CM to send response, unless disconnect is
     * notified to application where it takes care of sending response. */
    cmData->sendDiscRsp = TRUE;
#endif
    csrBtCmL2caDisconnectHandler(cmData,
                                 FALSE,
                                 prim->cid,
                                 prim->identifier,
                                 (CsrBtReasonCode) prim->reason);

#ifdef CSR_BT_INSTALL_CM_DISABLE_AUTO_DISC_RESP
    /* CSR_BT_INSTALL_CM_DISABLE_AUTO_DISC_RESP disables automatic response for
     * disconnect indication from Synergy CM when disconnect indication is sent to
     * application. Check if CM still needs to respond to Disconnect Indication,
     * for the cases when application is not notified. */
    if (cmData->sendDiscRsp)
#endif
    {
        if (cmData->recvMsgP)
        { /* The CM has not save the L2CA_DISCONNECT_IND message. Accept the release
            request initiated from a peer device right away                            */
            L2CA_DisconnectRsp(prim->identifier, prim->cid);
        }
        else
        { /* The CM has save L2CA_DISCONNECT_IND. Wait to send the response message     */
            ;
        }
    }
}

#endif /* #ifndef EXCLUDE_CSR_BT_L2CA_MODULE */
